#include "../config.h"

#include <glibmm/i18n.h>

#include "application.h"
#include "command.h"
#include "peercast.h"

namespace gpyp {
namespace peercast {

//========================
// Parser
void Parser::on_start_parsing() {
  conf::StackParser::on_start_parsing();
  relayed_ .clear();
  found_   .clear();
  servents_.clear();
  has_servents_ = false;
}
void Parser::on_complete_parsing() {
  conf::StackParser::on_complete_parsing();
  for(iterator iter = begin(); iter != end(); ++iter) {
    RefRelayedChannel rc = *iter;
    rc->fc_ = found_.lookup(rc->ch_->get_id());
    /* qt版はChannel::totalListeners()とChannel::totalRelays()を使用。たぶんこれで互換。 */
    rc->total_listeners_ = rc->ch_->get_listeners();
    if (rc->fc_) {
      rc->total_listeners_ += rc->fc_->listeners_;
      rc->total_relays_     = rc->fc_->hosts_;
    } else {
      rc->total_relays_     = rc->hosts_;
    }
  }
  for(ServentList::iterator iter = servents_.begin(); iter != servents_.end(); ++iter) {
    (*iter)->fc_ = found_.lookup((*iter)->id_);
  }
}
void Parser::on_error(GError *error) {
  log::err << "peercast::Parser: error";
  if (error && error->message) {
    log::err << " - " << error->message;
  }
  log::err << log::endl;
}

void Parser::onload_relayed_ch(const AttributesWrapper &/*attributes*/) {
  ch_relayed_.reset(new RelayedChannel());
  ch_relayed_->ch_.reset(new Channel(app_, NULL, relayed_.size() + 1));
  ch_ = ch_relayed_->ch_;
}
void Parser::unload_relayed_ch() {
  static const struct StatusTable {
    Status      status;
    const char *name;
  } status_table[] = {
#define STATUS(NAME) {STATUS_##NAME, #NAME}
    STATUS(NONE),
    STATUS(WAIT),
    STATUS(CONNECT),
    STATUS(REQUEST),
    STATUS(CLOSE),
    STATUS(RECEIVE),
    STATUS(BROADCAST),
    STATUS(ABORT),
    STATUS(SEARCH),
    STATUS(NOHOSTS),
    STATUS(IDLE),
    STATUS(ERROR),
    STATUS(NOTFOUND),
#undef STATUS
    {STATUS_INVALID, NULL}
  };
  for(const StatusTable *s = status_table; s->name; ++s) {
    if (ch_relayed_->ch_->get_status() == s->name) {
      ch_relayed_->status_ = s->status;
      break;
    }
  }
  relayed_.push_back(ch_relayed_);
}

void Parser::onload_found_ch(const AttributesWrapper &attributes) {
  ch_found_.reset(new FoundChannel());
  const char *id = attributes.get("id");
  if (id) ch_found_->id_.set(id);
}
void Parser::onload_found_ch_hits_host(const AttributesWrapper &/*attributes*/) {
  ch_found_->host_list_.push_back(ch_found_host_ = new Host());
}
void Parser::unload_found_ch() {
  found_.insert(ch_found_->id_, ch_found_);
}

void Parser::onload_servents(const AttributesWrapper &/*attributes*/) {
  has_servents_ = true;
}
void Parser::onload_servents_servent(const AttributesWrapper &/*attributes*/) {
  ch_servent_.reset(new Servent());
  servents_.push_back(ch_servent_);
}

Parser::Parser(Application &app) :
  app_          (app),
  ch_found_host_(NULL),
  has_servents_ (false)
{
#define ADD_ATTR(C, NS, NAME) (C).add_attr(#NAME, sigc::mem_fun(*this, &Parser::onattr_##NS##_##NAME))
  Context *pc = NULL, *c = NULL;
  pc = add("peercast");
  c = pc->add("channels_relayed");
  c = c->add("channel",
             sigc::mem_fun(*this, &Parser::onload_relayed_ch),
             sigc::mem_fun(*this, &Parser::unload_relayed_ch));
  setup_handler_channel_attr(*c);
  ADD_ATTR(*c, relayed_ch, uptime);
  ADD_ATTR(*c, relayed_ch, skips);
  ADD_ATTR(*c, relayed_ch, bcflags);
  setup_handler_track_attr(*c->add("track"));
  c = c->add("relay");
  setup_handler_relay_attr(*c);
  ADD_ATTR(*c, relayed_ch_relay, hosts);
  /* CRCKYL_EX */
  ADD_ATTR(*c, relayed_ch_relay, firewalled);
  ADD_ATTR(*c, relayed_ch_relay, relay);
  ADD_ATTR(*c, relayed_ch_relay, tracker);
  ADD_ATTR(*c, relayed_ch_relay, keep);

  c = pc->add("channels_found");
  c = c->add("channel",
             sigc::mem_fun(*this, &Parser::onload_found_ch),
             sigc::mem_fun(*this, &Parser::unload_found_ch));
  c = c->add("hits");
  ADD_ATTR(*c, found_ch_hits, hosts);
  ADD_ATTR(*c, found_ch_hits, listeners);
  ADD_ATTR(*c, found_ch_hits, relays);
  ADD_ATTR(*c, found_ch_hits, firewalled);
  ADD_ATTR(*c, found_ch_hits, closest);
  ADD_ATTR(*c, found_ch_hits, furthest);
  ADD_ATTR(*c, found_ch_hits, newest);
  c = c->add("host", sigc::mem_fun(*this, &Parser::onload_found_ch_hits_host));
  ADD_ATTR(*c, found_ch_hits_host, ip);
  ADD_ATTR(*c, found_ch_hits_host, hops);
  ADD_ATTR(*c, found_ch_hits_host, listeners);
  ADD_ATTR(*c, found_ch_hits_host, relays);
  ADD_ATTR(*c, found_ch_hits_host, uptime);
  ADD_ATTR(*c, found_ch_hits_host, push);
  ADD_ATTR(*c, found_ch_hits_host, relay);
  ADD_ATTR(*c, found_ch_hits_host, direct);
  ADD_ATTR(*c, found_ch_hits_host, cin);
  ADD_ATTR(*c, found_ch_hits_host, stable);
  ADD_ATTR(*c, found_ch_hits_host, version);
  ADD_ATTR(*c, found_ch_hits_host, versionvp);
  ADD_ATTR(*c, found_ch_hits_host, update);
  ADD_ATTR(*c, found_ch_hits_host, tracker);
  /* CRCKYL_EX */
  ADD_ATTR(*c, found_ch_hits_host, servid);

  c = pc->add("servents", sigc::mem_fun(*this, &Parser::onload_servents));
  c = c->add("servent", sigc::mem_fun(*this, &Parser::onload_servents_servent));
  ADD_ATTR(*c, servent, servid);
  ADD_ATTR(*c, servent, id);
  ADD_ATTR(*c, servent, ip);
  ADD_ATTR(*c, servent, type);
  ADD_ATTR(*c, servent, status);
  ADD_ATTR(*c, servent, lastconnect);
  ADD_ATTR(*c, servent, agent);
  ADD_ATTR(*c, servent, lastskiptime);
  ADD_ATTR(*c, servent, lastskipcount);
#undef ADD_ATTR
}

}/*peercast*/

//========================
// PCRelayView::Relays
void PCRelayView::Relays::prepare_commandopts(CommandOpts &opts) {
  RefRelayedChannel rc = get_selected();
  if (rc) {
    opts.relayed_ch = rc;

    RefChannel ch = app_.ypcollection()->find(rc->ch_->get_id());
    opts.ch = ch ? ch : rc->ch_;
  }
}
bool PCRelayView::Relays::on_popup(guint button, guint32 activate_time) {
  popup_menu_.popup(button, activate_time);
  return true;
}

#ifndef GTK_CELL_LAYOUT_HAVE_GET_CELLS
GtkCellRenderer *PCRelayView::Relays::get_tooltip_renderer(GtkTreeViewColumn *column) {
  if (column == column_name_) {
    return renderer_name_;
  } else if (column == column_bitrate_ ||
             column == column_status_ ||
             column == column_listeners_ ||
             column == column_keep_) {
    return GTK_CELL_RENDERER(renderer_other_.gobj());
  }
  return NULL;
}
#endif

void PCRelayView::Relays::cell_data_common(GtkCellRenderer  *cell,
                                           RefRelayedChannel rc) {
  using namespace peercast;
  const Gdk::Color *c = (rc->status_ == STATUS_BROADCAST
                         ? &TEXT_COLOR_ORANGE
                         : (rc->status_ == STATUS_RECEIVE && rc->tracker_
                            ? &TEXT_COLOR_GREEN
                            : NULL));
  if (c) {
    g_object_set(cell, "foreground-gdk", c->gobj(), NULL);
  } else {
    g_object_set(cell, "foreground-set", FALSE, NULL);
  }
  g_object_set(cell,
               "underline", (rc->status_ == STATUS_BROADCAST
                             ? PANGO_UNDERLINE_DOUBLE
                             : (rc->status_ == STATUS_RECEIVE && rc->tracker_
                                ? PANGO_UNDERLINE_SINGLE
                                : PANGO_UNDERLINE_NONE)),
               NULL);
}
void PCRelayView::Relays::_cell_data_name(GtkTreeViewColumn */*tree_column*/,
                                          GtkCellRenderer   *cell,
                                          GtkTreeModel      *tree_model,
                                          GtkTreeIter       *iter,
                                          Relays            *self) {
  using namespace peercast;
  Gtk::TreeIter tree_iter(tree_model, iter);
  RefRelayedChannel rc = tree_iter->get_value(self->model_column_channel_);
  g_object_set(cell,
               "text", rc->ch_->get_name().c_str(),
               "relay-gdk", (rc->status_ == STATUS_RECEIVE || rc->status_ == STATUS_BROADCAST
                             ? get_relay_color(true, rc->firewalled_, rc->total_relays_, rc->relay_)
                             : RELAY_COLOR_WHITE).gobj(),
               NULL);
  self->cell_data_common(cell, rc);
}
void PCRelayView::Relays::_cell_data_bitrate(GtkTreeViewColumn */*tree_column*/,
                                             GtkCellRenderer   *cell,
                                             GtkTreeModel      *tree_model,
                                             GtkTreeIter       *iter,
                                             Relays            *self) {
  Gtk::TreeIter tree_iter(tree_model, iter);
  RefRelayedChannel rc = tree_iter->get_value(self->model_column_channel_);
  g_object_set(cell,
               "text", rc->ch_->get_bitrate_str().c_str(),
               NULL);
  self->cell_data_common(cell, rc);
}
void PCRelayView::Relays::_cell_data_status(GtkTreeViewColumn */*tree_column*/,
                                            GtkCellRenderer   *cell,
                                            GtkTreeModel      *tree_model,
                                            GtkTreeIter       *iter,
                                            Relays            *self) {
  Gtk::TreeIter tree_iter(tree_model, iter);
  RefRelayedChannel rc = tree_iter->get_value(self->model_column_channel_);
  char buf[4] = {'\0', '\0', '\0', '\0'};
  strncpy(buf, rc->ch_->get_status().c_str(), 3);
  g_object_set(cell, "text", buf, NULL);
  self->cell_data_common(cell, rc);
}
void PCRelayView::Relays::_cell_data_listeners(GtkTreeViewColumn */*tree_column*/,
                                               GtkCellRenderer   *cell,
                                               GtkTreeModel      *tree_model,
                                               GtkTreeIter       *iter,
                                               Relays            *self) {
  Gtk::TreeIter tree_iter(tree_model, iter);
  RefRelayedChannel rc = tree_iter->get_value(self->model_column_channel_);
  g_object_set(cell,
               "text", format("%d/%d",
                              rc->total_listeners_,
                              rc->total_relays_).c_str(),
               NULL);
  self->cell_data_common(cell, rc);
}
void PCRelayView::Relays::_cell_data_keep(GtkTreeViewColumn */*tree_column*/,
                                          GtkCellRenderer   *cell,
                                          GtkTreeModel      *tree_model,
                                          GtkTreeIter       *iter,
                                          Relays            *self) {
  Gtk::TreeIter tree_iter(tree_model, iter);
  RefRelayedChannel rc = tree_iter->get_value(self->model_column_channel_);
  g_object_set(cell,
               "text", rc->keep_ ? "Y" : "N",
               NULL);
  self->cell_data_common(cell, rc);
}

void PCRelayView::Relays::status_icon_destroy(Glib::RefPtr<Gdk::Pixbuf> *icon) {
  delete icon;
}
Glib::RefPtr<Gdk::Pixbuf> PCRelayView::Relays::get_status_icon(peercast::Status status) {
  using namespace peercast;
  if (!status_icon_hash_) {
    status_icon_hash_ = g_hash_table_new_full(g_int_hash, g_int_equal,
                                              g_free, (GDestroyNotify)&Relays::status_icon_destroy);
  }
  Glib::RefPtr<Gdk::Pixbuf> *icon =
    (Glib::RefPtr<Gdk::Pixbuf> *)g_hash_table_lookup(status_icon_hash_, &status);
  if (icon) {
    return *icon;
  } else {
    Glib::RefPtr<Gdk::Pixbuf> *icon = new Glib::RefPtr<Gdk::Pixbuf>();
    switch(status) {
#define LOAD_ICON(STATUS, ICON)                                         \
      case STATUS_##STATUS:                                             \
        *icon = IconManager::load_icon(ICON, IconManager::ICON_SIZE_16); \
        break;
      LOAD_ICON(WAIT,      IconManager::PEERCAST_WAIT);
      LOAD_ICON(CONNECT,   IconManager::PEERCAST_CONNECT);
      LOAD_ICON(REQUEST,   IconManager::PEERCAST_CONNECT);
      LOAD_ICON(CLOSE,     IconManager::PEERCAST_CLOSE);
      LOAD_ICON(RECEIVE,   IconManager::PEERCAST_RECEIVE);
      LOAD_ICON(BROADCAST, IconManager::PEERCAST_BROADCAST);
      LOAD_ICON(ABORT,     IconManager::PEERCAST_ERROR);
      LOAD_ICON(SEARCH,    IconManager::PEERCAST_SEARCH);
      LOAD_ICON(NOHOSTS,   IconManager::PEERCAST_ERROR);
      LOAD_ICON(IDLE,      IconManager::PEERCAST_IDLE);
      LOAD_ICON(ERROR,     IconManager::PEERCAST_ERROR);
      LOAD_ICON(NOTFOUND,  IconManager::PEERCAST_ERROR);
    default:
      break;
#undef LOAD_ICON
    }

    int *s = (int *)g_malloc(sizeof(int));
    *s = status;
    g_hash_table_insert(status_icon_hash_, s, icon);
    return *icon;
  }
}

void PCRelayView::Relays::update(Parser &parser) {
  ID selected_id;
  Gtk::TreeIter selected = get_selection()->get_selected();
  if (selected) selected_id = selected->get_value(model_column_channel_)->ch_->get_id();

  store_scroll_value();

  liststore_ = Gtk::ListStore::create(columns_);
  bool has_keep = false;
  Gtk::TreeRowReference reselect_row;
  for(Parser::iterator iter = parser.begin(); iter != parser.end(); ++iter) {
    Gtk::TreeRow row = *liststore_->append();
    RefRelayedChannel rc = *iter;
    row[model_column_channel_]     = rc;
    row[model_column_status_icon_] = get_status_icon(rc->status_);
    if (rc->keep_ >= 0) has_keep = true;
    if (!reselect_row && (*iter)->ch_->get_id() == selected_id) {
      reselect_row = Gtk::TreeRowReference(liststore_, liststore_->get_path(row));
    }
  }
  gtk_tree_view_column_set_visible(column_keep_, has_keep);
  hide_tooltip();
  unset_model();
  set_model(liststore_);
  if (reselect_row) get_selection()->select(reselect_row.get_path());
}

PCRelayView::Relays::Relays(Application &app) :
  WidgetInput<TreeView>(app, app.conf().input()->pc_relay(), true),
  app_             (app),
  renderer_name_   (gtk_cell_renderer_relay_new()),
  column_name_     (NULL),
  column_bitrate_  (NULL),
  column_status_   (NULL),
  column_listeners_(NULL),
  column_keep_     (NULL),
  popup_menu_      (app_, app_.conf().menu()->pc_relay(),
                    sigc::mem_fun(*this, &Relays::prepare_commandopts)),
  status_icon_hash_(NULL)
{
  columns_.add(model_column_channel_);
  columns_.add(model_column_status_icon_);

  g_object_set(renderer_name_,
               "ellipsize", PANGO_ELLIPSIZE_END,
               "xpad", 0,
               "ypad", 0,
               NULL);
  column_name_ = gtk_tree_view_column_new_with_attributes("Channel", renderer_name_, NULL);
  gtk_tree_view_column_set_cell_data_func(column_name_,
                                          renderer_name_,
                                          (GtkTreeCellDataFunc)&Relays::_cell_data_name,
                                          this, NULL);
  gtk_tree_view_column_set_expand(column_name_, TRUE);
  gtk_tree_view_column_set_sizing(column_name_, GTK_TREE_VIEW_COLUMN_AUTOSIZE);

  renderer_other_.property_xalign() = 1;
  renderer_other_.property_xpad() = 0;
  renderer_other_.property_ypad() = 0;
#define COLUMN(C) {                                                     \
    column_##C##_ = gtk_tree_view_column_new();                         \
    gtk_tree_view_column_pack_start(column_##C##_,                      \
                                    GTK_CELL_RENDERER(renderer_other_.gobj()), \
                                    TRUE);                              \
    gtk_tree_view_column_set_cell_data_func(column_##C##_,              \
                                            GTK_CELL_RENDERER(renderer_other_.gobj()), \
                                            (GtkTreeCellDataFunc)&Relays::_cell_data_##C, \
                                            this, NULL);                \
    gtk_tree_view_column_set_sizing(column_##C##_, GTK_TREE_VIEW_COLUMN_AUTOSIZE); \
  }
  COLUMN(bitrate);
  //COLUMN(status);
  COLUMN(listeners);
  COLUMN(keep);
#undef COLUMN

  column_status_ = gtk_tree_view_column_new_with_attributes("Status",
                                                            GTK_CELL_RENDERER(renderer_status_icon_.gobj()),
                                                            "pixbuf", model_column_status_icon_.index(),
                                                            NULL);

  gtk_tree_view_append_column(gobj(), column_name_);
  gtk_tree_view_append_column(gobj(), column_bitrate_);
  gtk_tree_view_append_column(gobj(), column_listeners_);
  gtk_tree_view_append_column(gobj(), column_keep_);
  gtk_tree_view_append_column(gobj(), column_status_);

  set_headers_visible(false);
}
PCRelayView::Relays::~Relays() {
  if (status_icon_hash_) g_hash_table_destroy(status_icon_hash_);
}

//========================
// PCRelayView::Connections
void PCRelayView::Connections::prepare_commandopts(CommandOpts &opts) {
  RefServent s = get_selected();
  if (s) opts.servent = s;
  if (ch_) {
    RefChannel ch = app_.ypcollection()->find(ch_->ch_->get_id());
    opts.ch = ch ? ch : ch_->ch_;
  }
}
bool PCRelayView::Connections::on_popup(guint button, guint32 activate_time) {
  popup_menu_.popup(button, activate_time);
  return true;
}

#ifndef GTK_CELL_LAYOUT_HAVE_GET_CELLS
GtkCellRenderer *PCRelayView::Connections::get_tooltip_renderer(GtkTreeViewColumn *column) {
  if (column == column_) {
    return renderer_;
  }
  return NULL;
}
#endif

bool PCRelayView::Connections::visible_func(const Gtk::TreeIter &iter) {
  if (ch_) {
    return iter->get_value(model_column_servent_)->id_ == ch_->ch_->get_id();
  } else {
    return true;
  }
}

void PCRelayView::Connections::update(Parser &parser) {
  int selected_id = -1;
  Gtk::TreeIter selected = get_selection()->get_selected();
  if (selected) selected_id = selected->get_value(model_column_servent_)->servid_;

  store_scroll_value();

  liststore_ = Gtk::ListStore::create(columns_);
  Gtk::TreeRowReference reselect_row;
  Parser::ServentList &sl = parser.get_servents();
  for(Parser::ServentList::iterator iter = sl.begin(); iter != sl.end(); ++iter) {
    RefServent s = *iter;
    if (s->type_ == "NONE") continue;

    time_t t = s->lastconnect_ ? (time(NULL) - s->lastconnect_) : 0;
    bool available = false, relay = false, firewalled = false;
    size_t relays = 0, total_listeners = 0, total_relays = 0;
    char *vp_ver = NULL;

    if (s->fc_) {
      for(FoundChannel::iterator hiter = s->fc_->host_list_.begin();
          hiter != s->fc_->host_list_.end(); ++hiter) {
        if (hiter->servid_ == s->servid_) {
          if (hiter->hops_ == 1 && hiter->host_ == s->host_) {
            available  = true;
            relay      = hiter->relay_ > 0;
            firewalled = hiter->push_ > 0;
            relays     = hiter->relays_;
            if (hiter->versionvp_ > 0 && !vp_ver) {
              vp_ver = g_strdup_printf("(VP%04d)", hiter->versionvp_);
            }
          }
          total_listeners += hiter->listeners_;
          total_relays    += hiter->relays_;
        }
      }
    }

    Gtk::TreeRow row = *liststore_->append();
    row[model_column_servent_] = s;

    bool nabla = time(NULL) < s->lastskiptime_ + 120;
    char *skip = NULL;
    if (nabla) skip = g_strdup_printf("(%d)", s->lastskipcount_);

    bool relaying = false;
    /* ちょっとコンパクト版 */
    if (s->type_ == "RELAY") {
      relaying = true;
      if (s->status_ == "CONNECTED") {
        row[model_column_text_] = format("%sRELAYING-%ds-%d/%d-%s-%s%s",
                                         skip ? skip : "",
                                         t,
                                         total_listeners,
                                         total_relays,
                                         s->ip_.c_str(),
                                         s->agent_.c_str(),
                                         vp_ver ? vp_ver : "");
      } else {
        row[model_column_text_] = format("%s%s-%s-%ds-%d/%d-%s-%s%s",
                                         skip ? skip : "",
                                         s->type_.c_str(),
                                         s->status_.c_str(),
                                         t,
                                         total_listeners,
                                         total_relays,
                                         s->ip_.c_str(),
                                         s->agent_.c_str(),
                                         vp_ver ? vp_ver : "");
      }
    } else {
      if (s->status_ == "CONNECTED" ||
          s->type_ == "DIRECT") {
        row[model_column_text_] = format("%s%s-%s-%ds-%s-%s",
                                         skip ? skip : "",
                                         s->type_.c_str(),
                                         s->status_.c_str(),
                                         t,
                                         s->ip_.c_str(),
                                         s->agent_.c_str());
      } else {
        row[model_column_text_] = format("%s%s-%s-%ds-%s",
                                         skip ? skip : "",
                                         s->type_.c_str(),
                                         s->status_.c_str(),
                                         t,
                                         s->ip_.c_str());
      }
    }
    row[model_column_color_relay_] = (relaying
                                      ? get_relay_color(available, firewalled, relays, relay)
                                      : RELAY_COLOR_WHITE);
    row[model_column_nabla_] = nabla;
    if (vp_ver) g_free(vp_ver);
    if (skip)   g_free(skip);

    if (!reselect_row && s->servid_ == selected_id) {
      reselect_row = Gtk::TreeRowReference(liststore_, liststore_->get_path(row));
    }
  }

  filter_ = Gtk::TreeModelFilter::create(liststore_);
  filter_->set_visible_func(sigc::mem_fun(*this, &Connections::visible_func));
  hide_tooltip();
  unset_model();
  set_model(filter_);
  if (reselect_row) {
    Gtk::TreePath path = filter_->convert_child_path_to_path(reselect_row.get_path());
    if (!path.empty()) get_selection()->select(path);
  }
}

void PCRelayView::Connections::set(RefRelayedChannel ch) {
  ch_ = ch;
  if (filter_) filter_->refilter();
}

PCRelayView::Connections::Connections(Application &app) :
  WidgetInput<TreeView>(app, app.conf().input()->pc_connection(), true),
  app_       (app),
  renderer_  (gtk_cell_renderer_relay_new()),
  column_    (NULL),
  popup_menu_(app_, app_.conf().menu()->pc_connection(),
              sigc::mem_fun(*this, &Connections::prepare_commandopts))
{
  columns_.add(model_column_servent_);
  columns_.add(model_column_color_relay_);
  columns_.add(model_column_nabla_);
  columns_.add(model_column_text_);

  g_object_set(renderer_,
               "xpad", 0,
               "ypad", 0,
               "ellipsize", PANGO_ELLIPSIZE_END,
               NULL);
  column_ = gtk_tree_view_column_new_with_attributes("Connection",
                                                     renderer_,
                                                     "relay-gdk", model_column_color_relay_.index(),
                                                     "nabla",     model_column_nabla_.index(),
                                                     "text",      model_column_text_.index(),
                                                     NULL);
  gtk_tree_view_append_column(gobj(), column_);

  set_headers_visible(false);
}

//========================
// PCRelayView::ConnectPage
PCRelayView::ConnectPage::ConnectPage(Application &app) :
  Gtk::HBox  (true),
  l_message_ (_("Not connected")),
  l_host_    (_("Server"),   Gtk::ALIGN_LEFT),
  d_host_    (app.conf().network()->peercast_server()),
  l_password_(_("Password"), Gtk::ALIGN_LEFT),
  connect_   (_("Connect"))
{
  Gtk::Table *t1 = Gtk::manage(new Gtk::Table());
  t1->attach(l_host_,     0, 1, 0, 1, Gtk::FILL | Gtk::SHRINK, Gtk::FILL | Gtk::SHRINK);
  t1->attach(d_host_,     1, 2, 0, 1, Gtk::FILL | Gtk::SHRINK, Gtk::FILL | Gtk::SHRINK);
  t1->attach(l_password_, 0, 1, 1, 2, Gtk::FILL | Gtk::SHRINK, Gtk::FILL | Gtk::SHRINK);
  t1->attach(d_password_, 1, 2, 1, 2, Gtk::FILL | Gtk::SHRINK, Gtk::FILL | Gtk::SHRINK);
  t1->set_col_spacings(8);
  t1->set_row_spacings(2);
  Gtk::HBox *h1 = Gtk::manage(new Gtk::HBox());
  h1->pack_end(connect_, false, false);
  Gtk::VBox *v1 = Gtk::manage(new Gtk::VBox(false, 8));
  v1->pack_start(l_message_, false, false);
  v1->pack_start(*t1, false, false);
  v1->pack_start(*h1, false, false);
  pack_start(*v1, false, false);

  d_password_.set_visibility(false);
  app.conf().network()->peercast_server_pw()->attach(d_password_.property_text());

  set_border_width(8);
}

//========================
// PCRelayView
const Gdk::Color PCRelayView::RELAY_COLOR_GREEN ("#00ff00");
const Gdk::Color PCRelayView::RELAY_COLOR_BLUE  ("#00a0ff");
const Gdk::Color PCRelayView::RELAY_COLOR_PURPLE("#ff00ff");
const Gdk::Color PCRelayView::RELAY_COLOR_ORANGE("#ff8000");
const Gdk::Color PCRelayView::RELAY_COLOR_RED   ("#ff0000");
const Gdk::Color PCRelayView::RELAY_COLOR_BLACK ("#000000");
const Gdk::Color PCRelayView::RELAY_COLOR_WHITE ("#ffffff");

const Gdk::Color PCRelayView::TEXT_COLOR_ORANGE ("#808000");
const Gdk::Color PCRelayView::TEXT_COLOR_GREEN  ("#00a000");
const Gdk::Color PCRelayView::TEXT_COLOR_BLACK  ("#000000");

void PCRelayView::loader_on_complete(const loader::RefResponse response) {
  loader_connection_.reset();
  if (parser_.parse(response->get_source())) {
    conn_channel_selected_.block();
    relays_.update(parser_);
    if (parser_.get_has_servents()) {
      connections_.update(parser_);
      box_right_.show();
    } else {
      box_right_.hide();
    }
    conn_channel_selected_.unblock();
    relays_selection_on_changed();
    pane_->show();
    connect_page_.hide();
    error_count_ = 0;
    if (*conf_->connected()) set_update_timer();
  } else {
    ++error_count_;
    if (error_count_ > 3) {
      show_connect_page(_("Failed to parse XML"));
    } else if (*conf_->connected()) {
      set_update_timer();
    }
  }
}
void PCRelayView::loader_on_error(const loader::RefError error) {
  log::err << "PCRelayView: " << error->get_message() << log::endl;
  loader_connection_.reset();
  ++error_count_;
  if (error_count_ > 3) {
    show_connect_page(error->get_message());
  } else if (*conf_->connected()) {
    set_update_timer();
  }
}

void PCRelayView::set_update_timer() {
  conn_update_timeout_.disconnect();
  conn_update_timeout_ = Glib::signal_timeout()
    .connect(sigc::mem_fun(*this, &PCRelayView::update_timeout), *conf_->update_interval() * 1000);
  connect_page_.set_sensitive(false);
}
bool PCRelayView::update_timeout() {
  conn_update_timeout_.disconnect();
  if (loader_connection_) loader_connection_->cancel();
  try {
    URL url("http://" + *app_.conf().network()->peercast_server() + "/admin?cmd=viewxml");
    loader_connection_ = loader_.req(url);
    loader_connection_->load();
  } catch(const InvalidURL &ex) {
    log::err << ex.what() << log::endl;
    show_connect_page(ex.what());
  }
  return false;
}

void PCRelayView::relays_selection_on_changed() {
  RefRelayedChannel rc = relays_.get_selected();
  //buttons_left_.set_sensitive(rc);
  connections_.set(rc);
}
/*
void PCRelayView::connections_selection_on_changed() {
  RefServent s = connections_.get_selected();
  buttons_right_.set_sensitive(s);
}
*/

void PCRelayView::request_channel_command(RefRelayedChannel rc, const std::string &command) {
  using namespace loader;
  try {
    URL url("http://" + *app_.conf().network()->peercast_server() +
            "/admin?cmd=" + command + "&id=" + rc->ch_->get_id_str());
    Pool::request(url, Pool::SlotComplete(), Pool::SlotError())->load();
  } catch(const InvalidURL &ex) {
    log::err << ex.what() << log::endl;
  }
}
void PCRelayView::request_channel_command(const std::string &command) {
  RefRelayedChannel rc = relays_.get_selected();
  if (rc) request_channel_command(rc, command);
}
void PCRelayView::request_connection_disconnect(RefServent s) {
  using namespace loader;
  try {
    URL url("http://" + *app_.conf().network()->peercast_server() +
            "/admin?cmd=connect&c" + tostr(s->servid_) + "=1&stop");
    Pool::request(url, Pool::SlotComplete(), Pool::SlotError())->load();
  } catch(const InvalidURL &ex) {
    log::err << ex.what() << log::endl;
  }
}

void PCRelayView::channel_bump() {
  request_channel_command("bump");
}
void PCRelayView::channel_disconnect() {
  request_channel_command("stop");
}
void PCRelayView::channel_keep() {
  request_channel_command("keep");
}
void PCRelayView::connection_disconnect() {
  RefServent s = connections_.get_selected();
  if (s) request_connection_disconnect(s);
}

void PCRelayView::show_connect_page(const Glib::ustring &message) {
  conf_->connected() = false;
  conn_update_timeout_.disconnect();
  if (loader_connection_) loader_connection_->cancel();

  connect_page_.set_message(message);
  connect_page_.set_sensitive(true);
  connect_page_.show();
  pane_->hide();
}

void PCRelayView::activate() {
  connect_page_.set_sensitive(false);
  conf_->connected() = true;
  update_timeout();
}
void PCRelayView::deactivate() {
  show_connect_page(_("Not connected"));
}

bool PCRelayView::init_idle() {
  if (*conf_->connected()) activate();
  return false;
}

bool PCRelayView::bc_pc_activate(ShellLine &/*line*/, CommandOpts &/*opts*/) {
  activate();
  return true;
}
bool PCRelayView::bc_pc_deactivate(ShellLine &/*line*/, CommandOpts &/*opts*/) {
  deactivate();
  return true;
}

bool PCRelayView::bc_pc_relay_bump(ShellLine &/*line*/, CommandOpts &opts) {
  if (opts.relayed_ch) {
    request_channel_command(opts.relayed_ch, "bump");
    return true;
  }
  return false;
}
bool PCRelayView::bc_pc_relay_disconnect(ShellLine &/*line*/, CommandOpts &opts) {
  if (opts.relayed_ch) {
    request_channel_command(opts.relayed_ch, "stop");
    return true;
  }
  return false;
}
bool PCRelayView::bc_pc_relay_keep(ShellLine &/*line*/, CommandOpts &opts) {
  if (opts.relayed_ch) {
    request_channel_command(opts.relayed_ch, "keep");
    return true;
  }
  return false;
}

bool PCRelayView::bc_pc_connection_disconnect(ShellLine &/*line*/, CommandOpts &opts) {
  if (opts.servent) {
    request_connection_disconnect(opts.servent);
    return true;
  }
  return false;
}

PCRelayView::PCRelayView(Application &app, RefPCRelayViewElement conf) :
  panel::Page<Gtk::VBox>(conf),
  panel::Paned     (*(pane_ = new Gtk::HPaned()), conf),
  app_             (app),
  conf_            (conf),
  connect_page_    (app_),
  relays_          (app_),
  connections_     (app_),
  //buttons_left_    (Gtk::BUTTONBOX_START, 2),
  //buttons_right_   (Gtk::BUTTONBOX_START, 2),
  box_left_        (false, 2),
  box_right_       (false, 2),
  loader_          (app_),
  parser_          (app_),
  error_count_     (0),
  bc_pc_           (*app_.bc().add_ns("PeerCast")),
  bc_pc_relay_     (*bc_pc_.add_ns("Relay")),
  bc_pc_connection_(*bc_pc_.add_ns("Connection"))
{
  connect_page_.signal_connected().connect(sigc::mem_fun(*this, &PCRelayView::activate));

  Gtk::ScrolledWindow *sw1 = Gtk::manage(new Gtk::ScrolledWindow());
  sw1->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
  sw1->add(relays_);
  //Gtk::Button *b1 = Gtk::manage(new Gtk::Button(_("Bump")));
  //Gtk::Button *b2 = Gtk::manage(new Gtk::Button(_("Disconnect")));
  //Gtk::Button *b3 = Gtk::manage(new Gtk::Button(_("Keep")));
  //b1->signal_clicked().connect(sigc::mem_fun(*this, &PCRelayView::channel_bump));
  //b2->signal_clicked().connect(sigc::mem_fun(*this, &PCRelayView::channel_disconnect));
  //b3->signal_clicked().connect(sigc::mem_fun(*this, &PCRelayView::channel_keep));
  //buttons_left_.pack_start(*b1);
  //buttons_left_.pack_start(*b2);
  //buttons_left_.pack_start(*b3);
  //buttons_left_.set_border_width(2);
  box_left_.pack_start(*sw1);
  //box_left_.pack_start(buttons_left_, false, false);

  Gtk::ScrolledWindow *sw2 = Gtk::manage(new Gtk::ScrolledWindow());
  sw2->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
  sw2->add(connections_);
  //Gtk::Button *b4 = Gtk::manage(new Gtk::Button(_("Disconnect")));
  //b4->signal_clicked().connect(sigc::mem_fun(*this, &PCRelayView::connection_disconnect));
  //buttons_right_.pack_start(*b4);
  //buttons_right_.set_border_width(2);
  box_right_.pack_start(*sw2);
  //box_right_.pack_start(buttons_right_, false, false);

  panel::Paned::pack1(box_left_,  false, false);
  panel::Paned::pack2(box_right_, true,  false);

  //panel::Paned::pack1(relays_,      false, false, true);
  //panel::Paned::pack2(connections_, true,  false, true);

  connect_page_.show_all();
  connect_page_.set_no_show_all(true);

  pane_->show_all();
  pane_->hide();
  pane_->set_no_show_all(true);

  pack_start(connect_page_);
  pack_start(*pane_);

  conn_channel_selected_ = relays_.get_selection()->signal_changed()
    .connect(sigc::mem_fun(*this, &PCRelayView::relays_selection_on_changed));
  //connections_.get_selection()->signal_changed()
  //  .connect(sigc::mem_fun(*this, &PCRelayView::connections_selection_on_changed));

  loader_.signal_complete()
    .connect(sigc::mem_fun(*this, &PCRelayView::loader_on_complete));
  loader_.signal_error()
    .connect(sigc::mem_fun(*this, &PCRelayView::loader_on_error));

#define ADD_CMD(NS, CMD, HELP)                                          \
  bc_##NS##_.add_cmd(#CMD, sigc::mem_fun(*this, &PCRelayView::bc_##NS##_##CMD), HELP)
  ADD_CMD(pc, activate,   _("Activate PeerCast"));
  ADD_CMD(pc, deactivate, _("Deactivate PeerCast"));

  ADD_CMD(pc_relay, bump,       _("Bump channel"));
  ADD_CMD(pc_relay, disconnect, _("Disconnect channel"));
  ADD_CMD(pc_relay, keep,       _("Toggle keep channel"));

  ADD_CMD(pc_connection, disconnect, _("Disconnect connection"));
#undef ADD_CMD

  Glib::signal_idle().connect(sigc::mem_fun(*this, &PCRelayView::init_idle));
}
PCRelayView::~PCRelayView() {
  app_.bc().remove(bc_pc_);
  conn_update_timeout_.disconnect();
  if (loader_connection_) loader_connection_->cancel();
  delete pane_;
}

}/*gpyp*/
